package io.demo.message.benchmark;

import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.demo.message.domain.Customer;
import io.demo.message.util.TestingModels;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

class SpeedBenchmarks {

  private final Customer customer;

  private final List<Benchmark> benchmarks;

  private final ThreadFactory threadFactory = new ThreadFactoryBuilder()
      .setNameFormat("Benchmark-executor-%d")
      .setUncaughtExceptionHandler(new ExceptionHandler(Runtime.getRuntime()))
      .build();

  private final int threadCount = useProcessors();

  SpeedBenchmarks() {
    customer = TestingModels.customer();
    benchmarks = ImmutableList.of(
        new JdkBenchmark(),
        new ProtobufBenchmark(),
        new KryoBenchmark(),
        new Hessian2Benchmark(),
        new JacksonBenchmark(),
        new GsonBenchmark(),
        new FastjsonBenchmark());
  }

  void execute() throws InterruptedException {
    ExecutorService threadPool = Executors.newFixedThreadPool(threadCount, threadFactory);
    try {
      IntStream.of(1_000, 5_000, 20_000, 50_000, 200_000).forEach(looping ->
          benchmarks.forEach(benchmark ->
              threadPool.execute(() -> benchmark.execute(customer, looping)))
      );
    } finally {
      threadPool.shutdown();
      threadPool.awaitTermination(400, TimeUnit.SECONDS);
    }
  }

  private int useProcessors() {
    final int cpuCount = Runtime.getRuntime().availableProcessors();

    // Use less cpu if possible. I'm not going to slowdown my computer.
    if (cpuCount > 8) {
      return cpuCount / 2;
    }

    if (cpuCount > 2) {
      return cpuCount - 1;
    }
    // unless it's inevitable.
    return 1;
  }

  static final class ExceptionHandler implements UncaughtExceptionHandler {

    private static final Logger LOGGER = LoggerFactory
        .getLogger(SpeedBenchmarks.ExceptionHandler.class);

    private final Runtime runtime;

    ExceptionHandler(Runtime runtime) {
      this.runtime = runtime;
    }

    @Override
    public void uncaughtException(Thread t, Throwable e) {
      try {
        LOGGER.error("Caught an exception in {}.  Shutting down.", t);
        LOGGER.error("{}", e);
      } finally {
        runtime.exit(1);
      }
    }
  }
}
